using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;

namespace Parser
{
    /// <summary>
	/// Fundamental class which represents an expression. You can use your own constants
	/// in your formulas. Any existing conctant is available to every instance of expression class.
	/// By default you have defined the following constants:
	/// pi - 3.14159265
	/// e  - 2.71828183
	/// </summary>
	public class Expression
	{
		private class Constant
		{
		    public double Value { get; set; }
			
            public String Name { get; set; }

			public Constant(String N, double V)
			{
				Value = V;
				Name = N;
			}
		}

		private string definition;
        
        private string onp;

        private readonly IList<Constant> constants = new List<Constant>();

        private static readonly char [] supportedOperators = new [] {'+', '-', '*', '/', '(', ')', '^'};

        private static readonly string [] supportedFunctions = new [] {
                "sin",
                "cos",
                "tan",
                "cot",
                "sec",
                "csc",
                "asin",
                "acos",
                "atan",
                "acot",
                "sinh",
                "cosh",
                "tanh",
                "coth",
                "sech",
                "csch",
                "ln",
                "abs"
            };

        private static readonly string [] supportedDescriptions = new [] {
                "sine",
                "cosine",
                "tangens",
                "cotangens",
                "secans",
                "cosecans",
                "arcus sine",
                "arcus cosine",
                "arcus tangens",
                "arcus cotangens",
                "sine hyperbolic",
                "cosine hyperbolic",
                "tangens hyperbolic",
                "cotangens hyperbolic",
                "secans hyperbolic",
                "cosecans hyperbolic",
                "natural logarithm",
                "absolute value"
            };

		/// <summary>
		/// Contructor of the class used to define the formula while creating an object. 
		/// You can use the default constructor and specify the formula later.
		/// </summary>
		/// <param name="def">Your formula</param>
		public Expression(String def)
		{
            SetExpression(def);
		}

		/// <summary>
		/// Default constructor.
		/// </summary>
		public Expression()
		{
			definition = "";
            onp = "";
		}

        ///<summary>
        ///</summary>
        ///<param name="expression"></param>
        ///<param name="initConstants"></param>
        public Expression(string expression, IEnumerable<string> initConstants)
        {
            foreach (string s in initConstants) SetConstant(s, 0); 
            SetExpression(expression);
        }

        /// <summary>
		/// You can specify your formula using this method. 
		/// </summary>
		/// <param name="def">You formula</param>
		public void SetExpression(string def)
		{
            try
            {
                definition = def;
                onp = GetONP();
                Validate();
            }
            catch (ParserException)
            {
                definition = null;
                onp = null;
                throw;
            }
		}

		/// <summary>
		/// Gets the formula.
		/// </summary>
		public string GetExpression()
		{
			return definition;
		}

		/// <summary>
		/// You can delete a constant by specifying it's name.
		/// </summary>
		/// <param name="N">Name of the constant</param>
		public void DeleteConstant(String N)
		{
			// if ( N == "e" || N == "pi" ) throw new ArgumentException("error: Cannot delete default constants");

		    foreach (Constant constant in constants)
		    {
                if (constant.Name == N) constants.Remove(constant);
		    }
		}

        ///// <summary>
        ///// You can delete a contant by specifying it's index.
        ///// </summary>
        ///// <param name="k">Index</param>
        //public void DeleteConstant(int k)
        //{
        //    if ( k > 1) throw new ArgumentException("error: Cannot delete default constants");
        //    constants.RemoveAt(k);
        //}

		/// <summary>
		/// Gets the count of defined constants.
		/// </summary>
		public int GetConstantCount()
		{
			return constants.Count;
		}

		/// <summary>
		/// Sets a value to a constant. If a constants exists the value will be updated. 
		/// Otherwise it will be added to the list.
		/// </summary>
		/// <param name="N">Name of the constant</param>
		/// <param name="V">New value</param>
		public void SetConstant(String N, double V)
		{
			if ( N.IndexOf(' ') >= 0 ) throw new ArgumentException("error: Variable name cannot contain spaces");

			bool found = false;

		    foreach (Constant constant in constants)
		    {
                if (constant.Name == N)
                {
                    found = true;
                    constant.Value = V;
                }
		    }

			if (found) return;

			constants.Add(new Constant(N,V));
		}

		/// <summary>
		/// Gets the value of a constant specified by name.
		/// </summary>
		/// <param name="N">Name of the constant to retrive</param>
		/// <returns>Value of the constant</returns>
		public double GetValue(String N)
		{
            if (N.ToLower() == "e" ) return Math.E;
            if (N.ToLower() == "pi") return Math.PI;

		    foreach (Constant constant in constants)
		    {
                if (constant.Name == N) return constant.Value;
		    }
			
			throw new Exception("error: Constant not found");
		}
		
        ///// <summary>
        ///// Gets the value of a constant specified by index.
        ///// </summary>
        ///// <param name="k">Index of the constant</param>
        ///// <returns>Value of the constant</returns>
        //public static double GetValue(int k)
        //{
        //    if ( k >= constants.Count && k < 0) throw new ArgumentException("error: Constant index out of range");

        //    return constants[k].Value;		
        //}

        ///// <summary>
        ///// Gets the name of the contant at specified position.
        ///// </summary>
        ///// <param name="k">Item index</param>
        ///// <returns></returns>
        //public static string GetName(int k)
        //{
        //    if ( k >= constants.Count && k < 0) throw new ArgumentException("error: Constant index out of range");

        //    return constants[k].Name;
        //}

		/// <summary>
		/// Deletes all constants except pi and e
		/// </summary>
		public void ClearConstants()
		{
			constants.Clear();
		}

		private static bool OperatorCheck(char a)
		{
		    return Array.IndexOf(supportedOperators, a) >= 0;
		}

        /// <summary>
        /// Returns supported functions list.
        /// </summary>
        /// <returns>Supported functions list</returns>
        public static List<String> GetSupportedFunctions()
        {
            return new List<string>(supportedFunctions);
        }

        /// Returns supported functions list.
        /// </summary>
        /// <returns>Supported functions list</returns>
        public static List<String> GetSupportedFunctionsDescriptions()
        {
            return new List<string>(supportedDescriptions);
        }

		private static bool FunctionCheck(String a)
		{
		    return Array.IndexOf(supportedFunctions, a) >= 0;
		}

        private static int CharacterPriority(char a) 
		{
			int res;

            switch ( a ) 
			{
				case '+' : res = 1; break;
				case '-' : res = 1; break;
				case '*' : res = 2; break;
				case '/' : res = 2; break;
				case '(' : res = 0; break;
				case ')' : res = 1; break;
				case '^' : res = 2; break;
				default  : res =-1; break;
			}

			return res;
		}

		/// <summary>
		/// Gets the ONP of a formula. Pieces of the expression are separated by commas.
		/// </summary>
		/// <returns></returns>
		public string GetONP()
		{

			if ( definition == "" ) throw new ParserException("error: Expression is empty");

			Stack stack = new Stack();
			StringWriter onpBuffer = new StringWriter();
			StringReader expressionReader = new StringReader(definition);
			
            int bracesOnStack = 0;
			int fInside = 0;
			int last = 0;
			int unary = 0;

			while ( expressionReader.Peek() != -1)
			{
				StringWriter temp = new StringWriter();
				
                while ( !OperatorCheck ( (char) expressionReader.Peek() ) && expressionReader.Peek() != -1 ) 
				{
					if ( expressionReader.Peek() != ' ') temp.Write( (char) expressionReader.Read() );
					else expressionReader.Read();
				}

				if ( temp.ToString() != "" || unary != 0) 
				{
					last = 2;
					onpBuffer.Write(temp.ToString());

					if (!string.IsNullOrEmpty(temp.ToString())) if (temp.ToString()[0] != '(' ) unary = 0; 

					if ( unary != 0 ) 
					{
						onpBuffer.Write((char)expressionReader.Read());
						fInside = bracesOnStack;
						stack.Push( '(' );
					}

					if (FunctionCheck ( temp.ToString() ) ) 
					{
						onpBuffer.Write((char)expressionReader.Read());
						fInside = bracesOnStack;
						stack.Push( '(' );
					}

					if (unary == 0 && !FunctionCheck ( temp.ToString() ) ) 
                        onpBuffer.Write("|");

					unary = 0;
				}

				if ( OperatorCheck ( (char) expressionReader.Peek() ) ) 
				{
					unary = 0;
					char o = (char) expressionReader.Read();

					if ( o == '(' )
					{

						last = 0;
						stack.Push( o );
						bracesOnStack++;
					}

					if ( o == ')' )
					{

						while ( CharacterPriority((char)stack.Peek()) >= 1) onpBuffer.Write((char)stack.Pop()+ "|");
						stack.Pop();
						if ( bracesOnStack == fInside ) { onpBuffer.Write(o + "|"); bracesOnStack++; fInside = 0; }
						bracesOnStack--;
					}

					if ( o != ')' && o != '(' ) 
					{
						if ( o == '-'  && last <2 )
                        {
                            onpBuffer.Write( o ); 
                            unary = 1; 
                        }
                        else if (stack.Count == 0)
                        {
                            stack.Push(o);
                        }
                        else if ( CharacterPriority((char)stack.Peek()) < CharacterPriority(o))
                        {
                            stack.Push(o);
                        }
                        else 
                        {
                            while (stack.Count != 0 && CharacterPriority((char)stack.Peek()) >= CharacterPriority(o)) onpBuffer.Write((char)stack.Pop() + "|");
                            stack.Push(o);
                        }
						last = 1;
					}
				}
			}

			while ( stack.Count > 0 ) 
				if ( CharacterPriority((char)stack.Peek()) >= 1) onpBuffer.Write((char)stack.Pop() + "|"); else stack.Pop() ;

			return onpBuffer.ToString();
		}

		/// <summary>
		/// Checks whether used formula can be evaluated. If not an exception is thrown.
		/// </summary>
		/// <returns>0 if correct.</returns>
		public int Validate()
		{
			if (string.IsNullOrEmpty(definition)) throw new ParserException("error: Expression is empty");
			
			// ckeck whether there are enough parenthesises
			int k;
            string expr = "";
			
            for ( k = 0; k < definition.Length ; k++ ) if ( definition[k] != ' ') expr += definition[k];

			int last = 0;
			int leftcount = 0, rightcount = 0;

			for( int i=0; i < expr.Length; i++) 
			{
				if (expr[i] == '(') 
				{
					leftcount++;
				}
				if (expr[i] == ')') 
				{
					rightcount++;
				}
			}

			if ( leftcount != rightcount ) 
			{
				throw new ParserException(0, expr.Length, "error: Missing parenthases");
			}

			//checking whether functions exist

			int start = 0;

		    while ( start < expr.Length ) 
			{
				if ( !OperatorCheck( expr[start] ) )
				{
					int end = start;
					
                    while ( end < expr.Length && expr[end] != '(' && !OperatorCheck( expr[end] ) ) 
					{
						end++;
					}

                    //function found
					if ( end < expr.Length && expr[end] == '(' ) 
					{ 

						string fun = "";
						int  i;
						
                        for( i = start; i < end; i++ ) fun += expr[i];
						
                        if ( !FunctionCheck(fun) ) 
						{
							string message = "error: " + fun + " function unsupported";
							throw new ParserException(start, end, message);
						}
					}

                    // item found
					if ( end >= expr.Length  || (OperatorCheck( expr[end] ) && expr[end] != '(' ) )
					{            
						string fun = "";
						int i;
						
                        for( i = start; i < end; i++ ) fun += expr[i];
						
						try
						{
							double.Parse(fun);
						} 
						catch(Exception) 
						{
								try 
								{
									GetValue(fun);
								}
								catch (Exception) 
								{
									string message = " error: " + fun + " unknown item"; 
									throw new ParserException(start,end,message);
								}
						}
					}

					start = end;
					last = 0;

				}
				else 
				{
					start++;
					if (start >= expr.Length) 
					{
						if ( expr[start-1]!=')' )
							last =1;
					} 
					else 
					{
						if ( OperatorCheck( expr[start]) && expr[start-1] != '(' && expr[start-1] != ')' && expr[start] != '(' && expr[start] != '-' )
							last = 1;
						if (  expr[start-1] != '-' && expr[start-1] != '(' && expr[start-1] != ')' && ( (OperatorCheck(expr[start-2]) && expr[start-2] != ')') || start < 2) )
							last = 1;
					}

				}


				if ( last != 0) 
				{
					throw new ParserException(start-1,start,"error: Missing operator argument");
				}
			}

			//checking syntax A+B

			if ( last != 0) 
			{
				throw new ParserException(start-1,start,"error: Missing operator argument");
			}

			return 0;
		}

		private double ParseString(string a)
		{
		    int lenght = 0;
			int notConvert = 0 ;
			
            while ( lenght < a.Length ) 
			{
				if ( a[lenght] == '|' || a[lenght] == '(' || a[lenght] == ')') notConvert++;
				lenght++;
			}

			//checking if it is a number

			string temp = "";
			
            for ( int i=0; i < lenght-1 ; i++) temp += a[i];
			
            if ( notConvert <= 1) 
			{
				try
				{
                    return double.Parse(temp);
				}
				catch { }
                
			}
			//checking whether is on a constant list

			if ( constants.Count > 0 && notConvert <= 1) 
			{
				int minus = 1;
				
                if ( temp[0] == '-' ) 
				{
					minus = -1;
					temp = temp.Substring(1,temp.Length-1);
				}

				return minus*GetValue(temp);
			}

			//checking operators functions
			if ( temp[lenght-2] == ')' ) 
			{
				int firstBrace;
				int minus = 0;

				//finding where the inner expression begins
				for( firstBrace = 0; temp[firstBrace] != '(' ; firstBrace++);
				
                if (temp[0] == '-') minus = 1;

				string tempNext = "";
				string function = "";

				//reading the function
				for (int i = minus; i < firstBrace; i++) function += temp[i];

				//reading the inner expression
				for (int i = 0; i < lenght-firstBrace-3; i++) tempNext += temp[i+firstBrace+1];

				double arg = ParseString(tempNext);

				double result = 0;

                switch (function)
                {
                    case "":
                        result = arg;
                        break;
                    case "sin":
                        result = Math.Sin(arg);
                        break;
                    case "cos":
                        result = Math.Cos(arg);
                        break;
                    case "tan":
                        result = Math.Tan(arg);
                        break;
                    case "cot":
                        result = 1 / Math.Tan(arg);
                        break;
                    case "sec":
                        result = 1 / Math.Sin(arg);
                        break;
                    case "csc":
                        result = 1 / Math.Cos(arg);
                        break;
                    case "asin":
                        if (arg > 1 || arg < -1) throw new ParserException("error: arcus sine domain error");
                        result = Math.Asin(arg);
                        break;
                    case "acos":
                        if (arg > 1 || arg < -1) throw new ParserException("error: arcus cosine domain error");
                        result = Math.Acos(arg);
                        break;
                    case "atan":
                        result = Math.Atan(arg);
                        break;
                    case "acot":
                        result = 1.570796327 - Math.Atan(arg);
                        break;
                    case "sinh":
                        result = Math.Sinh(arg);
                        break;
                    case "cosh":
                        result = Math.Cosh(arg);
                        break;
                    case "tanh":
                        result = Math.Tanh(arg);
                        break;
                    case "coth":
                        result = 1 / Math.Tanh(arg);
                        break;
                    case "abs":
                        result = arg;
                        if (arg < 0) result *= -1;
                        break;
                    case "ln":
                        if (arg <= 0) throw new ParserException("error: Logarythm domain error");
                        result = Math.Log(arg);
                        break;
                }

				if (minus != 0) result*=-1;

				return result;

				//unknown function
			}

			//parsing the algebraic expression

			Stack tempStack = new Stack();
			int braces = 0;
			int tempi = 0;
			
            while ( tempi < temp.Length )
			{
				string test = "";
				int i = 0;

				//reading arguments
				while ( tempi < temp.Length && (temp[tempi] != '|' || braces != 0)) 
				{

					if ( temp[tempi] == '(' ) braces++;
					if ( temp[tempi] == ')' ) braces--;

					test += temp[tempi];
					i++;
					tempi++;

				}

				test += '|';
				i++;

				//reading the operator
				if ( !OperatorCheck(test[0]) || (test[0] == '-' && test[1] != '|' )) tempStack.Push( ParseString(test) );

				else 
				{
					//performing action

					double x = (double)tempStack.Pop();  
					double y = (double)tempStack.Pop();  

					if ( test[0] == '+' ) tempStack.Push(x+y);
					if ( test[0] == '-' ) tempStack.Push(y-x);
					if ( test[0] == '*' ) tempStack.Push(x*y);
					if ( test[0] == '/' ) 
					{
						if ( x == 0 && y == 0 ) throw new ParserException("error: Cannot divide 0/0");
						if ( x == 0 && y != 0 ) throw new ParserException("error: Cannot divide by 0");
						
                        tempStack.Push(y/x);
					}
					if ( test[0] == '^' ) 
					{
						if ( y<0 && x != (int) x ) throw new ParserException("error: Power has to be an integer");
						tempStack.Push( Math.Pow( y, x ) );
					}
				}
				if ( tempi < temp.Length ) tempi++;
			}

            return (double) tempStack.Pop();
		}

		/// <summary>
		/// Calculates the value of used formula. For performance reasons, the expression in not validated.
		/// </summary>
		/// <returns></returns>
		public double Parse()
		{
			if ( onp == "" ) throw new ParserException("error: Expression is empty");
			return ParseString(onp);
		}
	}
}
